luci-mod-network: implement Virtual Routing and Forwarding (VRF) options
authorPaul Donald <[email protected]>
Thu, 7 Nov 2024 20:21:07 +0000 (21:21 +0100)
committerPaul Donald <[email protected]>
Mon, 16 Jun 2025 22:08:17 +0000 (00:08 +0200)
VRF in netifd is now in main. See:
https://github.com/openwrt/netifd/pull/38/
https://github.com/openwrt/openwrt/commit/15c2ca0a834752cc9505751fc6d2f51861d34dfd

VRF netifd management was added to 24.10 in
https://github.com/openwrt/openwrt/pull/19125

Signed-off-by: Paul Donald <[email protected]>
modules/luci-mod-network/htdocs/luci-static/resources/tools/network.js
modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js

index 82696cf1fb37b0c37e40359a683dc3bb1dc7b88c..d106e7cdd0b28c61b108d11422a0f1f92c5dbe87 100644 (file)
@@ -440,7 +440,7 @@ return baseclass.extend({
                return s.taboption(tabName, optionClass, optionName, optionTitle, optionDescription);
        },
 
-       addDeviceOptions: function(s, dev, isNew) {
+       addDeviceOptions: function(s, dev, isNew, rtTables) {
                var parent_dev = dev ? dev.getParent() : null,
                    devname = dev ? dev.getName() : null,
                    o, ss;
@@ -451,8 +451,10 @@ return baseclass.extend({
                s.tab('bridgevlan', _('Bridge VLAN filtering'));
 
                o = this.replaceOption(s, 'devgeneral', form.ListValue, 'type', _('Device type'),
-                       !L.hasSystemFeature('bonding') && isNew ? '<a href="' + L.url("admin", "system", "package-manager", "?query=kmod-bonding") + '">'+
-                        _('For bonding, install %s').format('<code>kmod-bonding</code>') + '</a>' : null);
+                       (!L.hasSystemFeature('bonding') && isNew ? '<a href="' + L.url("admin", "system", "package-manager", "?query=kmod-bonding") + '">'+
+                        _('For bonding, install %s').format('<code>kmod-bonding</code>') + '</a><br/>' : '') +
+                       (!L.hasSystemFeature('vrf') && isNew  ? '<a href="' + L.url("admin", "system", "package-manager", "?query=kmod-vrf") + '">'+
+                        _('For VRF, install %s').format('<code>kmod-vrf</code>') + '</a><br/>' : ''));
                o.readonly = !isNew;
                o.value('', _('Network device'));
                if (L.hasSystemFeature('bonding')) {
@@ -463,8 +465,11 @@ return baseclass.extend({
                o.value('8021ad', _('VLAN (802.1ad)'));
                o.value('macvlan', _('MAC VLAN'));
                o.value('veth', _('Virtual Ethernet'));
+               if (L.hasSystemFeature('vrf') && L.hasSystemFeature('netifd_vrf')) {
+                       o.value('vrf', _('VRF device'));
+               }
                o.validate = function(section_id, value) {
-                       if (value == 'bonding' || value == 'bridge' || value == 'veth')
+                       if (value == 'bonding' || value == 'bridge' || value == 'veth' || value == 'vrf')
                                updatePlaceholders(this.section.getOption('name_complex'), section_id);
 
                        return true;
@@ -972,7 +977,7 @@ return baseclass.extend({
                o.datatype = 'uinteger';
                o.depends({'type': 'bonding', 'monitor_mode': 'mii'});
 
-               o = this.replaceOption(s, 'devgeneral', widgets.DeviceSelect, 'ifname_multi-bridge', _('Bridge ports'));
+               o = this.replaceOption(s, 'devgeneral', widgets.DeviceSelect, 'ifname_multi-bridge', dev?.getType() == 'bridge' ? _('Bridge ports'): _('Ports'));
                o.size = 10;
                o.rmempty = true;
                o.multiple = true;
@@ -998,7 +1003,8 @@ return baseclass.extend({
 
                        return (!parent_dev || parent_dev.getName() != bridge_name);
                };
-               o.description = _('Specifies the wired ports to attach to this bridge. In order to attach wireless networks, choose the associated interface as network in the wireless settings.')
+               o.description = dev?.getType() == 'bridge' ? _('Specifies the wired ports to attach to this bridge. In order to attach wireless networks, choose the associated interface as network in the wireless settings.'):
+                       _('Specifies the devices to attach to this VRF. In order to attach wireless networks, choose the associated interface as network in the wireless settings.');
                o.onchange = function(ev, section_id, values) {
                        ss.updatePorts(values);
 
@@ -1007,6 +1013,13 @@ return baseclass.extend({
                        });
                };
                o.depends('type', 'bridge');
+               o.depends('type', 'vrf');
+
+               o = this.replaceOption(s, 'devgeneral', form.Value, 'table', _('Routing table'));
+               rtTables.forEach((rtTable) => {
+                       o.value(rtTable[1], '%s (%d)'.format(rtTable[1], rtTable[0]));
+               })
+               o.depends('type', 'vrf');
 
                o = this.replaceOption(s, 'devgeneral', form.Flag, 'bridge_empty', _('Bring up empty bridge'), _('Bring up the bridge interface even if no ports are attached'));
                o.default = o.disabled;
index c59bc08abdee3c83bbd9142a1a4a86514ff3d761..1837aa08f8a7d748d74fd9cc8eed9e94c06ea968 100644 (file)
@@ -1400,7 +1400,7 @@ return view.extend({
                        var isNew = (uci.get('network', s.section, 'name') == null),
                            dev = getDevice(s.section);
 
-                       nettools.addDeviceOptions(s, dev, isNew);
+                       nettools.addDeviceOptions(s, dev, isNew, rtTables);
                };
 
                s.handleModalCancel = function(map /*, ... */) {
@@ -1473,6 +1473,9 @@ return view.extend({
                        case 'veth':
                                return 'veth';
 
+                       case 'vrf':
+                               return 'vrf';
+
                        case 'wifi':
                        case 'alias':
                        case 'switch':
@@ -1508,6 +1511,9 @@ return view.extend({
                        case 'veth':
                                return _('Virtual Ethernet');
 
+                       case 'vrf':
+                               return _('Virtual Routing and Forwarding (VRF)');
+
                        default:
                                return _('Network device');
                        }
@@ -1569,6 +1575,17 @@ return view.extend({
                        _('This prefix is randomly generated at first install.'));
                o.datatype = 'cidr6';
 
+               const l3mdevhelp1 = _('%s services running on this device in the default VRF context (ie., not bound to any VRF device) shall work across all VRF domains.');
+               const l3mdevhelp2 = _('Off means VRF traffic will be handled exclusively by sockets bound to VRFs.');
+
+               o = s.option(form.Flag, 'tcp_l3mdev', _('TCP Layer 3 Master Device (tcp_l3mdev) accept'),
+                       l3mdevhelp1.format('TCP') + '<br/>' +
+                       l3mdevhelp2);
+
+               o = s.option(form.Flag, 'udp_l3mdev', _('UDP Layer 3 Master Device (udp_l3mdev) accept'),
+                       l3mdevhelp1.format('UDP') + '<br/>' +
+                       l3mdevhelp2);
+
                o = s.option(form.ListValue, 'packet_steering', _('Packet Steering'), _('Enable packet steering across CPUs. May help or hinder network speed.'));
                o.value('0', _('Disabled'));
                o.value('1',_('Enabled'));